package com.example.demo.entity;

import java.util.*;

/**
 * @program:IntelliJ IDEA
 * @Description:染色体组
 * @author:Mr.CheCheHuiBujianO
 * @data: 2023/3/9 19:20
 */
public class GeneGroup {
    /*
    员工工作时间
        下表0：单次上班时长 （2<=时长<=4）
        下标1：每日工作时间  (时长<=8)
        下标2：每周上班时间  (<=40)
    */
    public Map<Staff, int[]> staffWorkingTime;
    /*
    种群组
        Gene记录着员工上班时间
     */
    private int groupSize; //种群大小
    private static float VARIATION = 0.9F;//变异概率
    private ArrayList<int[]> workTimeCount; //每个星期的每个时间段有多少人上班
    private Store store;//具体是那家店 
    private double fitnessValue = 0; //适应度的数值


    public GeneGroup() {
    }

    public Store getStore() {
        return store;
    }

    //获得适应度数值
    public double getFitnessValue() {
        return fitnessValue;
    }

    public ArrayList<int[]> getWorkTimeCount() {
        return workTimeCount;
    }

    /**
     * @Description: 传入当前的需要进行排班的店铺进行种群的初始化
     * @Param: [store]
     * @return:
     * @Author: Mr.Lin
     * @Date: 2023/3/15 12:44
     */
    public GeneGroup(Store store) {
        this.store = store;
        this.groupSize = store.staffList.size();
        this.staffWorkingTime = new HashMap<>();
        this.workTimeCount = new ArrayList<>();
        for (int i = 0; i < 7; i++) {
            workTimeCount.add(new int[Rule.businessHours]);
        }
        for (int i = 0; i < groupSize; i++) {
            Staff tmp = store.staffList.get(i);
            staffWorkingTime.put(tmp, new int[4]);
        }
    }

    //日期转化
    /**
     *@Description: 传一个日期返回这个日期是星期几
     *               星期一到六 2-7
     *               星期天是 1
     *@Param: [date]
     *@return:int
     *@Author: Mr.Lin
     *@Date: 2023/3/26 9:21
     */
    private static int dateConversion(Date date){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int weekDay = calendar.get(Calendar.DAY_OF_WEEK);
        return weekDay;
    }
    
    /**
     *@Description: 约束每次上班的最短时间 每次最多上班时间为两小时
     *@Param: [staff, Date]
     *@return:void
     *@Author: Mr.Lin
     *@Date: 2023/3/16 16:12
     */
    private void restrictedMinWindow(Staff staff, int Date) {
        //获取当前员工的工作时间
        int[] staffDailyWorking = staff.gene.chromoSome.get(Date);
        while (windowMin(staffDailyWorking) < Rule.onceClassesMin) {
            double va = Math.random() * Rule.businessHours;
            if (Date == 5 || Date == 6) {
                va = va < 2 ? 2 : va;
            } else {
                va = va > 23 ? 23 : va;
            }
            int v = (int) Math.ceil(va);
            staff.gene.chromoSome.get(Date)[v] = staff.gene.chromoSome.get(Date)[v] == 0 ? 1 : 0;
        }
    }


    /**
     * @Description: 放回窗口中连续的1出现次数最少的情况 利用滑动窗口
     * @Param: [staffDailyWorking]
     * @return:int
     * @Author: Mr.Lin
     * @Date: 2023/3/16 8:34
     */
    private int windowMin(int[] staffDailyWorking) {
        int left = 0;
        int right = 1;
        int res = Integer.MAX_VALUE;//记录窗口最小值
        while (right >= left && right < staffDailyWorking.length) {
            //右边窗口滑动
            if (staffDailyWorking[right] == 1) {
                right++;
            }
            //左边窗口滑动
            if (staffDailyWorking[left] == 0) {
                left++;
            }
            if (right < staffDailyWorking.length) {
                if (staffDailyWorking[right] == 0) {
                    if (staffDailyWorking[left] == 1) {
                        res = Math.min(right - left, res);
                    }
                    left = right;
                    right++;
                }
            }
        }
        return res;
    }


    /**
     * @Description: 窗口中最大连续不为0的长度
     * @Param: [staffDailyWorking]
     * @return:int
     * @Author: Mr.Lin
     * @Date: 2023/3/16 10:07
     */
    private int windowMax(int[] staffDailyWorking) {
        int once = 0;
        int res = 0;
        for (int i = 0; i < staffDailyWorking.length; i++) {
            once = staffDailyWorking[i] == 0 ? 0 : ++once;
            res = Math.max(once, res);
        }
        return res;
    }

    /**
     * @Description: 更新员工工作时间 根据员工的基因进行工作时间staffWorkingTime的更新
     * @Param: []
     * @return:void
     * @Author: Mr.Lin
     * @Date: 2023/3/15 18:56
     */
    public void renewStaffWorkingTime() {
        //获取员工列表
        List<Staff> staffList = store.staffList;
        //重置workTimeCount
        this.workTimeCount = new ArrayList<>();
        for (int i = 0; i < 7; i++) {
            workTimeCount.add(new int[Rule.businessHours]);
        }

        for (int i = 0; i < staffList.size(); i++) {
            //员工和其的数据进行绑定
            Staff staff = staffList.get(i);
            //重置tmp
            int[] tmp = staffWorkingTime.get(staff);
            tmp[0] = 0;
            tmp[1] = 0;
            //遍历员工每周的工作时间
            int onceWeekWorkTime = 0;
            //做测试
            restrictedMinWindow(staff,1);
            tmp[3] = windowMin(staff.gene.chromoSome.get(1));

            for (int j = 0; j < 7; j++) {
                //遍历员工每日的工作时间
                int nowStaffDayTime = 0; //员工每日工作小时
                int nowStaffOnceMax = windowMax(staff.gene.chromoSome.get(j)); //员工每次班次最多多少个小时
                //更新每日最长的班次
                tmp[0] = Math.max(nowStaffOnceMax, tmp[0]);
//                tmp[3] = windowMin(staff.gene.chromoSome.get(j));
                //遍历染色体
                for (int k = 0; k < staff.gene.chromoSome.get(j).length; k++) {
                    int AtOnDuty = staff.gene.chromoSome.get(j)[k];//0是在休息 1是在上班
                    //首先先更新每日工作时长
                    nowStaffDayTime = AtOnDuty == 1 ? ++nowStaffDayTime : nowStaffDayTime;
                    //记录每周工作时间
                    onceWeekWorkTime = AtOnDuty != 0 ? ++onceWeekWorkTime : onceWeekWorkTime;
                    //更新当前时间段的工作人员
                    workTimeCount.get(j)[k] = AtOnDuty == 1 ? ++workTimeCount.get(j)[k] : workTimeCount.get(j)[k];
                }
                tmp[1] = Math.max(nowStaffDayTime, tmp[1]);
            }
            tmp[2] = onceWeekWorkTime;
        }
    }

    /**
     *@Description: 传入特定日子的基因组 还有预期的需要员工 当满足预期需要员工时候 这一时间不再发生变异操作
     *@Param: [geneGroup, Date, expertNeedStuff]
     *@return:void
     *@Author: Mr.Lin
     *@Date: 2023/3/16 16:21
     */
    public void geneVariation(GeneGroup geneGroup, int Date,int[] expertNeedStuff) {
        /*
            进行种群的人进行遍历
                然后进行所有人的遍历 每次遍历进行随机数的再次随机
                过一个人的Gene
         */
        List<Staff> staffArr = geneGroup.store.staffList;

        for (int i = 0; i < geneGroup.groupSize; i++) {
            Staff staff = staffArr.get(i);
            int[] tmp = staff.gene.chromoSome.get(Date);
            if (Date == 6 || Date == 5) {
                for (int j = 2; j < Rule.businessHours; j++) {
                    if (expertNeedStuff[j] == workTimeCount.get(Date)[j])continue;
                    double va = Math.random() * 100 / 100;
                    if (va > VARIATION) {
                        tmp[j] = tmp[j] == 1 ? 0 : 1;
                    }
                }
            } else {
                for (int j = 0; j < Rule.businessHours - 2; j++) {
                    double va = Math.random() * 100 / 100;
                    if (expertNeedStuff[j] == workTimeCount.get(Date)[j])continue;
                    if (va > VARIATION) {
                        tmp[j] = tmp[j] == 1 ? 0 : 1;
                    }
                }
            }
        }

    }


    /**
     * @Description: 记录当前时间有无人值班
     * 有人值班就返回一个true
     * 这里应该可以进行适应度函数的改写
     * @Param: [geneGroup, store]
     * @return:boolean
     * @Author: Mr.Lin
     * @Date: 2023/3/10 23:11
     */
    private boolean adequateStaffOnDuty(int Date) {
        //获得当前日期的值班表
        int[] dayWorkOnDuty = workTimeCount.get(Date);
        if (Date == 5 || Date == 6) {
            for (int i = 2; i < dayWorkOnDuty.length; i++) {
                if (dayWorkOnDuty[i] == 0) {
                    changeDuty(Date, i);
                    return false;
                }
            }
        } else {
            for (int i = 0; i < dayWorkOnDuty.length - 2; i++) {
                if (dayWorkOnDuty[i] == 0) {
                    changeDuty(Date, i);
                    return false;
                }
            }
        }
        return true;
    }


    /**
     *@Description: 计算当前结果的适应度函数的数值是多少
     *@Param: [expertNeedStuff, Date]
     *@return:void
     *@Author: Mr.Lin
     *@Date: 2023/3/16 19:22
     */
    private void calculatedFitness(int[] expertNeedStuff,int Date){
        fitnessValue = 0;//重置适应度
        //传入当前时间段
        int[] workTime = workTimeCount.get(Date);
        for (int i = 0; i < expertNeedStuff.length; i++) {
            fitnessValue+=Math.pow(expertNeedStuff[i] - workTime[i],2);
        }
    }
    /**
     * @Description: 固定规则的约束 这里包括了每周工作时间 每日工作时间 每次工作时间的约束
     * @Param: [geneGroup]
     * @return:boolean
     * @Author: Mr.Lin
     * @Date: 2023/3/15 16:05
     */
    private boolean constraintFixedRule(GeneGroup geneGroup) {
        //先获得每日最长工作时长
        Map<Staff, int[]> staffWorkingTimeMap = geneGroup.staffWorkingTime;
        Rule rule = new Rule();
        for (Map.Entry<Staff, int[]> entry : staffWorkingTimeMap.entrySet()) {
            //获取当前员工的日工作时长 还有连续工作时长 还有一周工作时长
            int[] staffWorkingTimeArr = entry.getValue();
            int onceClass = staffWorkingTimeArr[0];
            int dailyWorkTime = staffWorkingTimeArr[1];
            int weekWorkTime = staffWorkingTimeArr[2];
            int onceClassMin = staffWorkingTimeArr[3];
            //这里都是些违背了约束条件的数据
            if (onceClass > rule.onceClassesMax || onceClass < rule.onceClassesMin || dailyWorkTime > rule.dayWorkTime || weekWorkTime > rule.weekWorkLimit) {
                return false;
            }
        }
        return true;
    }

    /**
     * @Description: 将某一日某一时间段没人值班的时候 随机选取一个员工进行值班
     * @Param: [Date, timeQuantum]
     * @return:void
     * @Author: Mr.Lin
     * @Date: 2023/3/15 21:46
     */
    private void changeDuty(int Date, int timeQuantum) {
        List<Staff> staffList = store.staffList;
        while (workTimeCount.get(Date)[timeQuantum] == 0) {
            int va = (int) Math.ceil((Math.random() * staffList.size()));
            va = va == staffList.size() ? --va : va;
            int staffStatus = staffList.get(va).gene.chromoSome.get(Date)[timeQuantum];
            if (staffStatus == 0) {
                staffList.get(va).gene.chromoSome.get(Date)[timeQuantum] = 1;
                workTimeCount.get(Date)[timeQuantum]++;
            }
        }
    }

    
    /**
     *@Description: 进行适应 这里还未引入那个适应度函数 仅仅是满足规则
     *@Param: [geneGroup, Date, expertNeedStuff]
     *@return:boolean
     *@Author: Mr.Lin
     *@Date: 2023/3/16 19:13
     */
    public boolean fitness(GeneGroup geneGroup, int Date,int[] expertNeedStuff) {
        while (true) {
            if (constraintFixedRule(geneGroup) == true && adequateStaffOnDuty(Date) == true) {
                calculatedFitness(expertNeedStuff,Date);
                break;
            }
            geneGroup.geneVariation(geneGroup, Date,expertNeedStuff);//进行变异
            geneGroup.renewStaffWorkingTime();//更新员工的工作时间段
        }
        return true;
    }

}
